package com.app.ryjy.util;

import com.app.common.EncryptConstant;
import com.app.util.BCECUtil;
import com.app.util.GmBaseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECCurve.Fp;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

/**
 * 国密SM2工具类
 * @date 2024/10/14
 * @time 18:53
 * @since 1.0
 */
@Slf4j
public class Sm2Util {
    private static final BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
    private static final BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
    private static final BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
    private static final BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
    private static final BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    private static final BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    private static final ECCurve CURVE;
    private static final ECPoint G_POINT;
    private static final ECDomainParameters DOMAIN_PARAMS;

    private Sm2Util() {
        throw new IllegalStateException("Utility class");
    }

    static {
        CURVE = new Fp(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B);
        G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
        DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, BigInteger.ONE);
    }

    /**
     * 获取SM2秘钥对字符串
     * @return
     * @throws Exception
     */
    public static Map generateKeys() throws Exception {
        SecureRandom random = new SecureRandom();
        AsymmetricCipherKeyPair keyPair = BCECUtil.generateKeyPair(DOMAIN_PARAMS, random);
        Map<String, String> keyMap = new HashMap();
        //获取公钥
        ECPublicKeyParameters pubKey = (ECPublicKeyParameters)keyPair.getPublic();
        byte[] pubKeyX509Der = BCECUtil.convertEcPubKeyToX509Der(pubKey);
        String pubKeyX509Pem = BCECUtil.convertX509DerEcPubKeyToPem(pubKeyX509Der);
        keyMap.put(EncryptConstant.PUBLIC_KEY, pubKeyX509Pem);
        ECPrivateKeyParameters priKey = (ECPrivateKeyParameters)keyPair.getPrivate();
        byte[] priKeyPkcs8Der = BCECUtil.convertEcPriKeyToPkcs8Der(priKey, pubKey);
        String priKeyPkcs8Pem = BCECUtil.convertPkcs8DerEcPriKeyToPem(priKeyPkcs8Der);
        keyMap.put(EncryptConstant.PRIVATE_KEY, priKeyPkcs8Pem);
        return keyMap;
    }

    /**
     * 签名
     * @param priKey  秘钥对象
     * @param srcData 签名数据
     * @return
     * @throws Exception
     */
    public static String sign(ECPrivateKeyParameters priKey, String srcData) throws Exception{
        return sign(priKey, null, srcData);
    }

    /**
     * 签名验证
     * @param pubKey   公钥对象
     * @param srcData  待验证数据
     * @param sign  签名串
     * @return
     */
    public static boolean verify(ECPublicKeyParameters pubKey, String srcData, String sign) {
        return verify(pubKey, null, srcData, sign);
    }


    /**
     * 根据私钥字符串获取私钥对象
     * @param filePath 私钥串
     * @return
     * @throws Exception
     */
    public static ECPrivateKeyParameters pem2PrivateKey(String filePath) {
        try {
            String pemPriStr = GmBaseUtil.loadKeyFromFile(filePath);
            byte[] pkcs8Der = BCECUtil.convertPemToPkcs8DerEcPriKey(pemPriStr);
            return BCECUtil.convertPkcs1DerToEcPriKey(pkcs8Der);
        } catch (Exception var3) {
            log.error("sm2 Exception msg={} exception={}", var3.getMessage(), var3);
            return null;
        }
    }

    /**
     * 根据公钥字符串获取公钥对象
     * @param filePath 公钥字符串
     * @return
     * @throws Exception
     */
    public static ECPublicKeyParameters pem2PublicKey(String filePath) {
        try {
            String pemPriStr = GmBaseUtil.loadKeyFromFile(filePath);
            byte[] x509DerEcPubKey = BCECUtil.convertPemToX509DerEcPubKey(pemPriStr);
            return BCECUtil.convertX509DerToEcPrublicKey(x509DerEcPubKey);
        } catch (Exception var3) {
            log.error("sm2 Exception msg={} exception={}", var3.getMessage(), var3);
            return null;
        }
    }

    /**
     * 公钥加密
     * @param pubKey  公钥对象
     * @param srcData  加密对象
     * @return
     */
    public static String encrypt(ECPublicKeyParameters pubKey, String srcData)throws Exception {
        SM2Engine engine = new SM2Engine();
        ParametersWithRandom pwr = new ParametersWithRandom(pubKey, new SecureRandom());
        engine.init(true, pwr);
        byte[] srcByte = srcData.getBytes(StandardCharsets.UTF_8);
        return byte2Base64(engine.processBlock(srcByte, 0, srcByte.length));
    }

    /**
     * 私钥解密
     * @param priKey   私钥对象
     * @param sm2CipherText  密文
     * @return
     */
    public static String decrypt(ECPrivateKeyParameters priKey, String sm2CipherText)throws Exception{
        SM2Engine engine = new SM2Engine();
        engine.init(false, priKey);
        byte[] srcByte = base642Byte(sm2CipherText);
        return new String(engine.processBlock(srcByte, 0, srcByte.length), StandardCharsets.UTF_8);
    }


    private static String byte2Base64(byte[] bytes) {
        Base64 encoder = new Base64();
        return encoder.encodeAsString(bytes);
    }

    private static byte[] base642Byte(String base64Key) {
        Base64 decoder = new Base64();
        return decoder.decode(base64Key);
    }

    private static String sign(ECPrivateKeyParameters priKey, byte[] withId, String srcData)throws Exception {
        SM2Signer signer = new SM2Signer();
        ParametersWithRandom pwr = new ParametersWithRandom(priKey, new SecureRandom());
        Object param;
        if (withId != null) {
            param = new ParametersWithID(pwr, withId);
        } else {
            param = pwr;
        }
        signer.init(true, (CipherParameters)param);
        byte[] srcByte = srcData.getBytes(StandardCharsets.UTF_8);
        signer.update(srcByte, 0, srcByte.length);
        return byte2Base64(signer.generateSignature());
    }

    private static boolean verify(ECPublicKeyParameters pubKey, byte[] withId, String srcData, String sign) {
        SM2Signer signer = new SM2Signer();
        Object param;
        if (withId != null) {
            param = new ParametersWithID(pubKey, withId);
        } else {
            param = pubKey;
        }
        signer.init(false, (CipherParameters)param);
        byte[] srcByte = srcData.getBytes(StandardCharsets.UTF_8);
        signer.update(srcByte, 0, srcByte.length);
        return signer.verifySignature(base642Byte(sign));
    }
}



